Prozkoumejte vzory JavaScript Proxy pro modifikaci chování objektů. Seznamte se s validací, virtualizací, sledováním a dalšími pokročilými technikami s příklady kódu.
JavaScript Proxy vzory: Zvládnutí modifikace chování objektů
Objekt JavaScript Proxy poskytuje výkonný mechanismus pro zachytávání a přizpůsobování základních operací s objekty. Tato schopnost otevírá dveře k široké škále návrhových vzorů a pokročilých technik pro řízení chování objektů. Tento komplexní průvodce zkoumá různé Proxy vzory a ilustruje jejich použití na praktických příkladech kódu.
Co je to JavaScript Proxy?
Objekt Proxy obaluje jiný objekt (cíl) a zachytává jeho operace. Tyto operace, známé jako pasti (traps), zahrnují vyhledávání vlastností, přiřazování, enumeraci a volání funkcí. Proxy vám umožňuje definovat vlastní logiku, která se provede před, po nebo místo těchto operací. Klíčovým konceptem Proxy je "metaprogramování", které vám umožňuje manipulovat s chováním samotného jazyka JavaScript.
Základní syntaxe pro vytvoření Proxy je:
const proxy = new Proxy(target, handler);
- target: Původní objekt, který chcete proxyovat.
- handler: Objekt obsahující metody (pasti), které definují, jak Proxy zachytává operace na cíli.
Běžné Proxy pasti (traps)
Objekt handler může definovat několik pastí. Zde jsou některé z nejčastěji používaných:
- get(target, property, receiver): Zachytává přístup k vlastnosti (např.
obj.property
). - set(target, property, value, receiver): Zachytává přiřazení vlastnosti (např.
obj.property = value
). - has(target, property): Zachytává operátor
in
(např.'property' in obj
). - deleteProperty(target, property): Zachytává operátor
delete
(např.delete obj.property
). - apply(target, thisArg, argumentsList): Zachytává volání funkcí (když je cíl funkce).
- construct(target, argumentsList, newTarget): Zachytává operátor
new
(když je cíl konstruktorová funkce). - getPrototypeOf(target): Zachytává volání
Object.getPrototypeOf()
. - setPrototypeOf(target, prototype): Zachytává volání
Object.setPrototypeOf()
. - isExtensible(target): Zachytává volání
Object.isExtensible()
. - preventExtensions(target): Zachytává volání
Object.preventExtensions()
. - getOwnPropertyDescriptor(target, property): Zachytává volání
Object.getOwnPropertyDescriptor()
. - defineProperty(target, property, descriptor): Zachytává volání
Object.defineProperty()
. - ownKeys(target): Zachytává volání
Object.getOwnPropertyNames()
aObject.getOwnPropertySymbols()
.
Proxy vzory a případy použití
Pojďme prozkoumat některé běžné Proxy vzory a jak je lze aplikovat v reálných scénářích:
1. Validace
Vzor Validace používá Proxy k vynucení omezení při přiřazování vlastností. To je užitečné pro zajištění integrity dat.
const validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('Věk není celé číslo');
}
if (value < 0) {
throw new RangeError('Věk musí být nezáporné celé číslo');
}
}
// Výchozí chování pro uložení hodnoty
obj[prop] = value;
// Indikace úspěchu
return true;
}
};
let person = {};
let proxy = new Proxy(person, validator);
proxy.age = 25; // Platné
console.log(proxy.age); // Výstup: 25
try {
proxy.age = 'young'; // Vyvolá TypeError
} catch (e) {
console.log(e); // Výstup: TypeError: Věk není celé číslo
}
try {
proxy.age = -10; // Vyvolá RangeError
} catch (e) {
console.log(e); // Výstup: RangeError: Věk musí být nezáporné celé číslo
}
Příklad: Představte si e-commerce platformu, kde je potřeba validovat uživatelská data. Proxy může vynucovat pravidla pro věk, formát e-mailu, sílu hesla a další pole, čímž zabraňuje uložení neplatných dat.
2. Virtualizace (Líné načítání)
Virtualizace, také známá jako líné načítání (lazy loading), odkládá načítání náročných zdrojů, dokud nejsou skutečně potřeba. Proxy může fungovat jako zástupný symbol pro skutečný objekt a načíst ho až při přístupu k jeho vlastnosti.
const expensiveData = {
load: function() {
console.log('Načítání náročných dat...');
// Simulace časově náročné operace (např. načítání z databáze)
return new Promise(resolve => {
setTimeout(() => {
resolve({
data: 'Toto jsou náročná data'
});
}, 2000);
});
}
};
const lazyLoadHandler = {
get: function(target, prop) {
if (prop === 'data') {
console.log('Přístup k datům, v případě potřeby je načítám...');
return target.load().then(result => {
target.data = result.data; // Uložení načtených dat
return result.data;
});
} else {
return target[prop];
}
}
};
const lazyData = new Proxy(expensiveData, lazyLoadHandler);
console.log('Počáteční přístup...');
lazyData.data.then(data => {
console.log('Data:', data); // Výstup: Data: Toto jsou náročná data
});
console.log('Následný přístup...');
lazyData.data.then(data => {
console.log('Data:', data); // Výstup: Data: Toto jsou náročná data (načteno z mezipaměti)
});
Příklad: Představte si velkou platformu sociálních médií s uživatelskými profily obsahujícími četné detaily a přidružená média. Načítání všech profilových dat najednou může být neefektivní. Virtualizace s Proxy umožňuje nejprve načíst základní informace o profilu a další podrobnosti nebo mediální obsah načíst až v okamžiku, kdy uživatel přejde do těchto sekcí.
3. Logování a sledování
Proxy lze použít ke sledování přístupu k vlastnostem a jejich modifikací. To je cenné pro ladění, auditování a monitorování výkonu.
const logHandler = {
get: function(target, prop, receiver) {
console.log(`GET ${prop}`);
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value) {
console.log(`SET ${prop} na ${value}`);
target[prop] = value;
return true;
}
};
let obj = { name: 'Alice' };
let proxy = new Proxy(obj, logHandler);
console.log(proxy.name); // Výstup: GET name, Alice
proxy.age = 30; // Výstup: SET age na 30
Příklad: V aplikaci pro kolaborativní úpravu dokumentů může Proxy sledovat každou změnu provedenou v obsahu dokumentu. To umožňuje vytvořit auditní stopu, povolit funkci zpět/znovu a poskytnout přehled o přispění uživatelů.
4. Pohledy pouze pro čtení
Proxy mohou vytvářet pohledy pouze pro čtení objektů, čímž zabraňují náhodným modifikacím. To je užitečné pro ochranu citlivých dat.
const readOnlyHandler = {
set: function(target, prop, value) {
console.error(`Nelze nastavit vlastnost ${prop}: objekt je pouze pro čtení`);
return false; // Indikace, že operace set selhala
},
deleteProperty: function(target, prop) {
console.error(`Nelze smazat vlastnost ${prop}: objekt je pouze pro čtení`);
return false; // Indikace, že operace delete selhala
}
};
let data = { name: 'Bob', age: 40 };
let readOnlyData = new Proxy(data, readOnlyHandler);
try {
readOnlyData.age = 41; // Vyvolá chybu
} catch (e) {
console.log(e); // Chyba není vyvolána, protože past 'set' vrací false.
}
try {
delete readOnlyData.name; // Vyvolá chybu
} catch (e) {
console.log(e); // Chyba není vyvolána, protože past 'deleteProperty' vrací false.
}
console.log(data.age); // Výstup: 40 (nezměněno)
Příklad: Zvažte finanční systém, kde někteří uživatelé mají přístup k informacím o účtu pouze pro čtení. Proxy lze použít k zabránění těmto uživatelům v úpravě zůstatků na účtech nebo jiných kritických dat.
5. Výchozí hodnoty
Proxy může poskytovat výchozí hodnoty pro chybějící vlastnosti. To zjednodušuje kód a zabraňuje kontrolám null/undefined.
const defaultValuesHandler = {
get: function(target, prop, receiver) {
if (!(prop in target)) {
console.log(`Vlastnost ${prop} nenalezena, vracím výchozí hodnotu.`);
return 'Výchozí hodnota'; // Nebo jakákoli jiná vhodná výchozí hodnota
}
return Reflect.get(target, prop, receiver);
}
};
let config = { apiUrl: 'https://api.example.com' };
let configWithDefaults = new Proxy(config, defaultValuesHandler);
console.log(configWithDefaults.apiUrl); // Výstup: https://api.example.com
console.log(configWithDefaults.timeout); // Výstup: Vlastnost timeout nenalezena, vracím výchozí hodnotu. Výchozí hodnota
Příklad: V systému pro správu konfigurace může Proxy poskytovat výchozí hodnoty pro chybějící nastavení. Například pokud konfigurační soubor neurčuje časový limit pro připojení k databázi, Proxy může vrátit předdefinovanou výchozí hodnotu.
6. Metadata a anotace
Proxy mohou k objektům připojovat metadata nebo anotace, čímž poskytují další informace bez úpravy původního objektu.
const metadataHandler = {
get: function(target, prop, receiver) {
if (prop === '__metadata__') {
return { description: 'Toto jsou metadata pro objekt' };
}
return Reflect.get(target, prop, receiver);
}
};
let article = { title: 'Úvod do Proxies', content: '...' };
let articleWithMetadata = new Proxy(article, metadataHandler);
console.log(articleWithMetadata.title); // Výstup: Úvod do Proxies
console.log(articleWithMetadata.__metadata__.description); // Výstup: Toto jsou metadata pro objekt
Příklad: V systému pro správu obsahu (CMS) může Proxy připojit k článkům metadata, jako jsou informace o autorovi, datum publikace a klíčová slova. Tato metadata lze použít pro vyhledávání, filtrování a kategorizaci obsahu.
7. Zachytávání funkcí
Proxy mohou zachytávat volání funkcí, což vám umožňuje přidat logování, validaci nebo jinou logiku před nebo po zpracování.
const functionInterceptor = {
apply: function(target, thisArg, argumentsList) {
console.log('Volání funkce s argumenty:', argumentsList);
const result = target.apply(thisArg, argumentsList);
console.log('Funkce vrátila:', result);
return result;
}
};
function add(a, b) {
return a + b;
}
let proxiedAdd = new Proxy(add, functionInterceptor);
let sum = proxiedAdd(5, 3); // Výstup: Volání funkce s argumenty: [5, 3], Funkce vrátila: 8
console.log(sum); // Výstup: 8
Příklad: V bankovní aplikaci může Proxy zachytávat volání transakčních funkcí, logovat každou transakci a provádět kontroly detekce podvodů před provedením transakce.
8. Zachytávání konstruktorů
Proxy mohou zachytávat volání konstruktorů, což vám umožňuje přizpůsobit vytváření objektů.
const constructorInterceptor = {
construct: function(target, argumentsList, newTarget) {
console.log('Vytváření nové instance', target.name, 's argumenty:', argumentsList);
const obj = new target(...argumentsList);
console.log('Nová instance vytvořena:', obj);
return obj;
}
};
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let ProxiedPerson = new Proxy(Person, constructorInterceptor);
let person = new ProxiedPerson('Alice', 28); // Výstup: Vytváření nové instance Person s argumenty: ['Alice', 28], Nová instance vytvořena: Person { name: 'Alice', age: 28 }
console.log(person);
Příklad: V rámci pro vývoj her může Proxy zachytávat vytváření herních objektů, automaticky jim přiřazovat jedinečná ID, přidávat výchozí komponenty a registrovat je v herním enginu.
Pokročilé úvahy
- Výkon: Ačkoliv Proxy nabízejí flexibilitu, mohou představovat zátěž na výkon. Je důležité provádět benchmarky a profilovat váš kód, abyste se ujistili, že výhody použití Proxy převažují nad náklady na výkon, zejména v aplikacích kritických na výkon.
- Kompatibilita: Proxy jsou relativně novým přírůstkem do JavaScriptu, takže starší prohlížeče je nemusí podporovat. Použijte detekci funkcí nebo polyfilly pro zajištění kompatibility se staršími prostředími.
- Odvolatelné Proxy: Metoda
Proxy.revocable()
vytváří Proxy, které lze odvolat. Odvolání Proxy zabrání zachytávání jakýchkoli dalších operací. To může být užitečné pro bezpečnostní účely nebo pro správu zdrojů. - Reflect API: Reflect API poskytuje metody pro provádění výchozího chování Proxy pastí. Použití
Reflect
zajišťuje, že se váš Proxy kód chová konzistentně se specifikací jazyka.
Závěr
JavaScript Proxy poskytují výkonný a všestranný mechanismus pro přizpůsobení chování objektů. Zvládnutím různých Proxy vzorů můžete psát robustnější, udržitelnější a efektivnější kód. Ať už implementujete validaci, virtualizaci, sledování nebo jiné pokročilé techniky, Proxy nabízejí flexibilní řešení pro řízení přístupu k objektům a jejich manipulaci. Vždy zvažte dopady na výkon a zajistěte kompatibilitu s vašimi cílovými prostředími. Proxy jsou klíčovým nástrojem v arzenálu moderního JavaScript vývojáře, který umožňuje výkonné metaprogramovací techniky.
Další zdroje
- Mozilla Developer Network (MDN): JavaScript Proxy
- Exploring JavaScript Proxies: Článek na Smashing Magazine